-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local log = require 'mc.logging'
local singleton = require 'mc.singleton'
local client = require 'storage.client'
local c_controller = require 'controller.controller_object'
local c_storageconfig = require 'storageconfig.storageconfig_object'
local signal = require 'mc.signal'
local class = require 'mc.class'
local c_drive_collection = require 'drive.drive_collection'
local common_def = require 'common_def'
local sml = require 'sml'
local utils = require 'mc.utils'
local method_misc = require 'method_misc'
local file_sec = require 'utils.file'

local controller_collection = class()

function controller_collection:ctor(persist)
    self.controller_list = {}
    self.on_add_controller = signal.new()
    self.on_del_controller = c_controller.on_delete_object
    self.on_os_state_changed = signal.new()
    self.persist = persist
    self.intf_map = {}
end

function controller_collection:dump_objs_info(fp_w)
    c_controller.collection:fold(function(acc, obj, key)
        fp_w:write(string.format("RAID Controller #%s Information\n", obj.Id))
        fp_w:write("==============================\n")
        obj:dump_info(fp_w)

        fp_w:write("Physical Drives Information\n")
        fp_w:write("--------------------------\n")
        c_drive_collection.get_instance():dump_controller_physical_drive(obj.Id, fp_w)
    end, {})

    fp_w:write("Pass Through Drives Information\n")
    fp_w:write("==============================\n")
    c_drive_collection.get_instance():dump_controller_physical_drive(255, fp_w)
    fp_w:write('\n\n\n')
end

function controller_collection:link_interface(controller)
    for _, intf in pairs(common_def.CONTROLLER_INTRERFACE) do
        local intf_obj = controller[intf]
        if intf_obj then
            self.intf_map[intf_obj] = controller
        end
    end
end

function controller_collection:init()
    -- 避免c_controller.on_add_object信号在对象初始化前已经分发过了
    c_controller.collection:fold(function(acc, obj, key)
        if self.controller_list[obj.Id] then
            return
        end

        c_storageconfig.get_instance().controller_mask =
            c_storageconfig.get_instance().controller_mask | (1 << obj.Id)
        c_storageconfig.get_instance():add_or_del_controller(obj, true)
        self.controller_list[obj.Id] = obj

        self.on_add_controller:emit(obj)
        self:link_interface(obj)
    end, {})

    c_controller.on_add_object:on(function(controller)
        if self.controller_list[controller.Id] then
            return
        end

        c_storageconfig.get_instance().controller_mask =
            c_storageconfig.get_instance().controller_mask | (1 << controller.Id)
        c_storageconfig.get_instance():add_or_del_controller(controller, true)
        self.controller_list[controller.Id] = controller
        self.on_add_controller:emit(controller)
        self:link_interface(controller)
    end)
    c_controller.on_delete_object:on(function(controller)
        self.controller_list[controller.Id] = nil
        c_storageconfig.get_instance():add_or_del_controller(controller, nil)
    end)

    self.on_os_state_changed:on(function(os_state)
        log:notice('os state changed to %s', os_state and 'ON' or 'OFF')
        c_controller.collection:fold(function(acc, obj, key)
            obj:change_tasks_state(os_state)
        end, {})
    end)

    client:OnPCIeCardPropertiesChanged(function(values, path, _)
        if values['PcbVersion'] == nil then
            return
        end
        local device_name = nil
        client:ForeachPCIeCardObjects(function(obj)
            if obj.path == path then
                device_name = obj.DeviceName
            end
        end)
        if not device_name then
            log:error('not found pcbversion changed controller, controller path:%s', path)
            return
        end
        local controller = self:get_by_controller_device_name(device_name)
        if not controller then
            log:error('not found controller object, controller device name:%s', device_name)
            return
        end
        controller.pcb_version = values['PcbVersion']:value()
    end)
end

function controller_collection:get_by_controller_device_name(device_name)
    local controller = c_controller.collection:find({ DeviceName = device_name })
    if not controller then
        log:notice('[Storage]Invalid Controller DeviceName: %s', device_name)
    end
    return controller
end

function controller_collection:get_by_controller_id(controller_id)
    local controller = c_controller.collection:find({ Id = controller_id })
    if not controller then
        log:notice('[Storage]Invalid Controller id: %d', controller_id)
        return nil
    end
    return controller
end

function controller_collection:get_all_controllers_id()
    local key_set = {}
    c_controller.collection:fold(function(acc, obj, key)
        key_set[#key_set + 1] = obj.Id
    end, {})

    return key_set
end

function controller_collection:get_all_controllers()
    local controller_set = {}
    c_controller.collection:fold(function(acc, obj, key)
        controller_set[#controller_set + 1] = obj
    end, {})

    return controller_set
end

return singleton(controller_collection)
